package com.xxl.sso.core.filter;

import com.xxl.sso.core.conf.Conf;
import com.xxl.sso.core.login.SsoWebLoginHelper;
import com.xxl.sso.core.path.impl.AntPathMatcher;
import com.xxl.sso.core.user.XxlSsoUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * web sso filter
 *
 * @author xuxueli 2018-04-03
 */
public class XxlSsoWebFilter extends HttpServlet implements Filter {
    private static Logger logger = LoggerFactory.getLogger(XxlSsoWebFilter.class);

    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private String ssoServer;
    private String logoutPath;
    private String excludedPaths;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        ssoServer = filterConfig.getInitParameter(Conf.SSO_SERVER);
        logoutPath = filterConfig.getInitParameter(Conf.SSO_LOGOUT_PATH);
        excludedPaths = filterConfig.getInitParameter(Conf.SSO_EXCLUDED_PATHS);

        logger.info("XxlSsoWebFilter init.");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        // make url
        String servletPath = req.getServletPath();

        // excluded path check
        if (excludedPaths!=null && excludedPaths.trim().length()>0) {
            for (String excludedPath:excludedPaths.split(",")) {
                String uriPattern = excludedPath.trim();

                // 支持ANT表达式
                if (antPathMatcher.match(uriPattern, servletPath)) {
                    // excluded path, allow
                    chain.doFilter(request, response);
                    return;
                }

            }
        }

        //如果是logout path，则删除客户端的cookie，并且重定向到sso-server的/logout 删除sso-server的cookie和redis中的用户数据
        // logout path check
        if (logoutPath!=null
                && logoutPath.trim().length()>0
                && logoutPath.equals(servletPath)) {

            // remove cookie
            SsoWebLoginHelper.removeSessionIdByCookie(req, res);

            // redirect logout
            String logoutPageUrl = ssoServer.concat(Conf.SSO_LOGOUT);// http://xxlssoserver.com:8080/xxl-sso-server/logout
            res.sendRedirect(logoutPageUrl);

            return;
        }

        // 如果以上情况都不是 则根据浏览器requestHeader中的此服务的cookie中的数据查询redis中是否存在用户数据，如果没有名为xxl_sso_sessionid的cookie 说明之前没有登录过此服务、而如果有名为xxl_sso_sessionid的cookie，但是redis中没有数据 说明登录过期 删除该cookie 重新登录 重定向到sso-server的/login?redirect_url=requestUrl
        // 如果url中的参数中有xxl_sso_sessionid，校验xxl_sso_sessionid（根据xxl_sso_sessionid查询redis中是否有用户数据、xxl_sso_sessionid的version是否和redis中的用户数据的version一致）校验成功，则需要创建当前服务的的cookie，这样就不需要每次登录该服务都重定向携带上sso-server的cookie去请求sso-server进行鉴权，减少了网络传输的时间 提高效率，创建成功后 放行
        // 这里xxlUser=null的原因：可能是因为cookie中就没有值（第一次请求）、长期未登录 redis过期（这里redis中的value的有效性只有24hrs，对token的时效性很有要求啊）、校验token的有效性失败
        // 上面3种情况都要请求sso-server进行重新登录
        // 注意：cookie在相同ip相同端口的情况下 是共享的，即这次请求：http://xxlssoclient1.com:8091/xxl-sso-web-sample-springboot/，下次请求：http://xxlssoclient2.com:8081/xxl-sso-web-sample-springboot/，浏览器携带的cookie是同一个（浏览器会自动判断哪个域名传哪个cookie、maxAge是否到期了）
        // 之后有其他服务访问需要登录才能访问的资源时，因为它没有它的cookie、而且url中的参数中也没有xxl_sso_sessionid，所以需要请求sso-server，这时因为之前已经有服务登录过sso-server了，所以requestHeader中会携带上sso-server的cookie，校验cookie成功后就返回到其他服务并且url中有个参数xxl_sso_sessionid，让其他服务校验xxl_sso_sessionid 校验成功 创建其他服务的cookie
        // valid login user, cookie + redirect
        XxlSsoUser xxlUser = SsoWebLoginHelper.loginCheck(req, res);

        // valid login fail
        if (xxlUser == null) {

            String header = req.getHeader("content-type");
            boolean isJson=  header!=null && header.contains("json");//TODO 这是什么操作？？？难道这就是ajax的异步?如果是请求内容是json 就是ajax异步请求数据 即请求的不是整个页面，所以不使用重定向？？？
            if (isJson) {

                // json msg
                res.setContentType("application/json;charset=utf-8");
                res.getWriter().println("{\"code\":"+Conf.SSO_LOGIN_FAIL_RESULT.getCode()+", \"msg\":\""+ Conf.SSO_LOGIN_FAIL_RESULT.getMsg() +"\"}");
                return;
            } else {//不是ajax异步请求 就可以使用重定向

                // total link
                String link = req.getRequestURL().toString();

                // redirect logout
                String loginPageUrl = ssoServer.concat(Conf.SSO_LOGIN)  // http://xxlssoserver.com:8080/xxl-sso-server/login?redirect_url=http://xxlssoserver.com:8081/xxl-sso-web-sample-springboot
                        + "?" + Conf.REDIRECT_URL + "=" + link;

                res.sendRedirect(loginPageUrl);
                return;
            }

        }

        // ser sso user
        request.setAttribute(Conf.SSO_USER, xxlUser);//一般客户端只会传递token 服务器根据token查询出redis中token映射的value 将value传递给controller层。然后controller层就可以通过调用request.getAttribute()来获取redis中的value了。


        // already login, allow
        chain.doFilter(request, response);// 鉴权成功，可以通过
        return;
    }

}
